/* Copyright Contributors to the Open Shading Language project.
 * SPDX-License-Identifier: BSD-3-Clause
 * https://github.com/imageworks/OpenShadingLanguage
 */


/** Lexical scanner for OpenShadingLanguage 'object' files
 **/


/************************************************************
 * Definitions section
 ************************************************************/


/* Option 'noyywrap' indicates that when EOF is hit, yyin does not 
 * automatically reset to another file.
 */
%option noyywrap
%option nounput noinput

/* never-interactive fixes a Windows compatibility problem where the
 * lexer emits isatty calls that don't exist.
 */
%option never-interactive

 /* Option 'prefix' creates a C++ lexer with the given prefix, so that
  * we can link with other flex-generated lexers in the same application
  * without name conflicts.
  */
%option prefix="oso"

 /* %option perf-report */


 /* Define regular expression macros 
  ************************************************/

 /* white space, not counting newline */
WHITE           [ \t\v\f\r]+
 /* alpha character */
ALPHA           [A-Za-z]
 /* numerals */
DIGIT           [0-9]
 /* Integer literal */
INTEGER         [-+]?{DIGIT}+
 /* floating point literal (E, FLT1, FLT2, FLT3 are just helpers) */
E               [eE][-+]?{DIGIT}+
FLT1            [-+]?{DIGIT}+\.{DIGIT}*{E}?
FLT2            [-+]?{DIGIT}*\.{DIGIT}+{E}?
FLT3            [-+]?{DIGIT}+{E}
FLT             {FLT1}|{FLT2}|{FLT3}
 /* string literal */
STR     \"(\\.|[^\\"\n])*\"
 /* Identifier: alphanumeric, may contain digits after the first character.
  * Also '$' and '.' are allowed!
  */
IDENT           ({ALPHA}|[_$])({ALPHA}|{DIGIT}|[_$\.])*
 /* C preprocessor (cpp) directives */
COMMENT         \#[^\n]*\n
 /* Hints */
IDORLITERAL     {FLT}|{STR}|{INTEGER}|({IDENT}(\[{INTEGER}?\])?)
HINTPATTERN     \%{IDENT}(\{({IDORLITERAL}(\,{IDORLITERAL})*)?\})?


 /* Note for lex newbies: the following '%{ .. %}' section contains literal
  * C code that will be inserted at the top of code that flex generates.
  */
%{
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <string>

#include <OpenImageIO/ustring.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/filesystem.h>

#include "osoreader.h"
using namespace OSL;
using namespace OSL::pvt;

#include "osogram.hpp"   /* Generated by bison/yacc */

#define yylval osolval

#ifdef _WIN32
#define YY_NO_UNISTD_H
#endif

#ifdef _MSC_VER
#include <io.h>
#define isatty _isatty
#endif

// flex itself will generate fatal warnings about signed vs unsigned.
// Bypass that nonsense.
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 6
#pragma GCC diagnostic ignored "-Wsign-compare"
#endif

// flex uses the 'register' keyword, warned because it's deprecated in C++17.
#if defined(__clang__)
#pragma GCC diagnostic ignored "-Wdeprecated-register"
#endif

%}

/* Declare modes */
%s DECLARATION


%%

 /************************************************
  * Lexical matching rules
  ************************************************/

 /* Comments */
{COMMENT}               {  /* skip it */ }

 /* keywords */
<DECLARATION>"closure"	{  return (yylval.i=CLOSURE); }
<DECLARATION>"color"	{  return (yylval.i=COLORTYPE); }
<DECLARATION>"float"	{  return (yylval.i=FLOATTYPE); }
<DECLARATION>"int"      {  return (yylval.i=INTTYPE); }
<DECLARATION>"matrix"	{  return (yylval.i=MATRIXTYPE); }
<DECLARATION>"normal"	{  return (yylval.i=NORMALTYPE); }
<DECLARATION>"point"	{  return (yylval.i=POINTTYPE); }
<DECLARATION>"string"	{  return (yylval.i=STRINGTYPE); }
<DECLARATION>"struct"	{  return (yylval.i=STRUCT); }
<DECLARATION>"vector"	{  return (yylval.i=VECTORTYPE); }

^local                  {
                           BEGIN (DECLARATION);
                           yylval.i = SymTypeLocal;
                           return SYMTYPE;
                        }

^temp                   {
                           BEGIN (DECLARATION);
                           yylval.i = SymTypeTemp;
                           return SYMTYPE;
                        }

^global                 {
                           BEGIN (DECLARATION);
                           yylval.i = SymTypeGlobal;
                           return SYMTYPE;
                        }

^param                  {
                           BEGIN (DECLARATION);
                           yylval.i = SymTypeParam;
                           return SYMTYPE;
                        }

^oparam                 {
                            BEGIN (DECLARATION);
                            yylval.i = SymTypeOutputParam;
                            return SYMTYPE;
                        }

^const                  {
                            BEGIN (DECLARATION);
                            yylval.i = SymTypeConst;
                            return SYMTYPE;
                        }

^code                   {
                            BEGIN (INITIAL);
                            return yylval.i = CODE;
                        }

 /* Identifiers */
{IDENT}	                {
                            yylval.s = ustring(yytext).c_str();
                            // std::cerr << "lex ident '" << yylval.s << "'\n";
                            return IDENTIFIER;
                        }

 /* Literal values */
{INTEGER}               {
                            yylval.i = OIIO::Strutil::from_string<int>(yytext);
                            // std::cerr << "lex int " << yylval.i << "\n";
                            return INT_LITERAL;
                        }

{FLT}                   {
                            yylval.f = OIIO::Strutil::from_string<float>(yytext);
                            // std::cerr << "lex float " << yylval.f << "\n";
                            return FLOAT_LITERAL;
                        }

{STR}                   {
                            // grab the material between the quotes
                            string_view s(yytext + 1, yyleng - 2);
                            std::string unescaped;
                            if (s.find('\\') != string_view::npos) {
                                // Only make a new string if we must unescape
                                unescaped = OIIO::Strutil::unescape_chars(s);
                                s = string_view(unescaped);
                            }
                            yylval.s = ustring(s).c_str();
                            // std::cerr << "osolex string '" << yylval.s << "'\n";
                            return STRING_LITERAL;
                        }

\%preprocessed_source\n(.*\n)* {
                            /* skip remainder of file */
                        }

{HINTPATTERN}           {
                            ustring s (yytext);
                            yylval.s = s.c_str();
                            return HINT;
                        }

 /* Ignore whitespace */
{WHITE} 		{  }

 /* Ignore a linefeed that ends with a trailing blackslash */
"\\\n"		        {  }

 /* End of line */
[\n]			{
                            OSOReader::osoreader->incr_lineno ();
                            return ENDOFLINE;
                        }

 /* catch-all rule for any other single characters */
.			{  return (yylval.i = *yytext); }

%%



OSL_NAMESPACE_ENTER

namespace pvt {   // OSL::pvt


OSOReader * OSOReader::osoreader = NULL;
static std::mutex osoread_mutex;



bool
OSOReader::parse_file (const std::string &filename)
{
    // The lexer/parser isn't thread-safe, so make sure Only one thread
    // can actually be reading a .oso file at a time.
    std::lock_guard<std::mutex> guard (osoread_mutex);

    // Force classic "C" locale for correct '.' decimal parsing.
    // N.B. This is not safe in a multi-threaded program where another
    // application thread is expecting the native locale to work properly.
    std::locale oldlocale;   // save the previous native locale
    std::locale::global (std::locale::classic());

    osoin = OIIO::Filesystem::fopen (filename, "r");
    if (! osoin) {
        m_err.error ("File %s not found", filename.c_str());
        return false;
    }

    osoreader = this;
    oso_switch_to_buffer (oso_create_buffer (osoin, YY_BUF_SIZE));
    int errcode = osoparse ();   // osoparse returns nonzero if error
    bool ok = ! errcode;   // osoparse returns nonzero if error
    if (ok) {
//        m_err.info ("Correctly parsed %s", filename.c_str());
    } else {
        m_err.error ("Failed parse of %s (error code %d)", filename.c_str(), errcode);
    }
    oso_delete_buffer (YY_CURRENT_BUFFER);
    fclose (osoin);
    std::locale::global (oldlocale);  // Restore the original locale.

    return ok;
}


bool
OSOReader::parse_memory (const std::string &buffer)
{
    // The lexer/parser isn't thread-safe, so make sure Only one thread
    // can actually be reading a .oso file at a time.
    std::lock_guard<std::mutex> guard (osoread_mutex);

#ifndef OIIO_STRUTIL_HAS_STOF
    // Force classic "C" locale for correct '.' decimal parsing.
    // N.B. This is not safe in a multi-threaded program where another
    // application thread is expecting the native locale to work properly.
    // This is not necessary for versions of OIIO that have Strutil::stof,
    // and we can remove it entirely when OIIO 1.9 is the minimum.
    std::locale oldlocale = std::locale::global (std::locale::classic());
#endif

    oso_switch_to_buffer (oso_scan_string (buffer.c_str()));
    osoreader = this;
    bool ok = ! osoparse ();   // osoparse returns nonzero if error
    if (ok) {
//        m_err.info ("Correctly parsed preloaded OSO code");
    } else {
        m_err.error ("Failed parse of preloaded OSO code");
    }
    oso_delete_buffer (YY_CURRENT_BUFFER);
#ifndef OIIO_STRUTIL_HAS_STOF
    std::locale::global (oldlocale);  // Restore the original locale.
#endif

    return ok;
}



}; // namespace pvt
OSL_NAMESPACE_EXIT
